package com.tianji.remark.service.impl;

import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.exceptions.DbException;
import com.tianji.common.utils.*;
import com.tianji.remark.constants.RedisConstants;
import com.tianji.remark.domain.dto.LikeRecordFormDTO;
import com.tianji.remark.domain.dto.LikedTimesDTO;
import com.tianji.remark.domain.po.LikedRecord;
import com.tianji.remark.mapper.LikedRecordMapper;
import com.tianji.remark.service.ILikedRecordService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 点赞记录表 服务实现类
 * </p>
 *
 * @author hzy
 * @since 2023-09-12
 */
@Service
@RequiredArgsConstructor
public class LikedRecordServiceImpl extends ServiceImpl<LikedRecordMapper, LikedRecord> implements ILikedRecordService {

    private final RabbitMqHelper mqHelper;

    private final StringRedisTemplate redisTemplate;

    /**
     * 点赞或取消
     *
     * @param dto
     */
    @Override
    public void isLikes(LikeRecordFormDTO dto) {
        if (dto == null) {
            throw new DbException("数据请求错误!!!");
        }

        Long userId = UserContext.getUser();
        //判断是点赞还是取消
        Boolean success = BooleanUtils.isTrue(dto.getLiked()) ? like(dto, userId) : notLike(dto, userId);
        if (success) {
            //>>>>>原始方法<<<<<<===================================================
            /*
                Integer count = lambdaQuery().eq(LikedRecord::getBizType, dto.getBizType())
                        .eq(LikedRecord::getBizId, dto.getBizId())
                        .count();
                if (count == null){
                    return;
                }
                //发送mq消息
                mqHelper.send(
                        MqConstants.Exchange.LIKE_RECORD_EXCHANGE,
                        StringUtils.format(MqConstants.Key.LIKED_TIMES_KEY_TEMPLATE,dto.getBizType()),
                        new LikedTimesDTO(dto.getBizId(),count)
                );
            */

            //=====================================================================
            //代码改造,redis优化 (+ mq消息中间件解耦合 --- 后面有的)
            //=====================================================================

            //成功,从redis中统计点赞数
            String key = RedisConstants.LIKES_BIZ_KEY_PREFIX + dto.getBizType() + ":" + dto.getBizId();
            Long size = redisTemplate.opsForSet().size(key);
            if (size == null) {
                return;
            }
            //将点赞数存入reids, 使用zset存储, 方便后期按照一定数量取出
            redisTemplate.opsForZSet().add(RedisConstants.LIKES_TIMES_KEY_PREFIX + dto.getBizType(), dto.getBizId().toString(), size);
        }
    }

    //取消点赞

    private Boolean notLike(LikeRecordFormDTO dto, Long userId) {
        String bizType = dto.getBizType();
        Long bizId = dto.getBizId();
        //>>>>>原始方法<<<<<<===================================================
        /*
        return lambdaUpdate().eq(LikedRecord::getUserId, userId)
                .eq(LikedRecord::getBizId, bizId)
                .eq(LikedRecord::getBizType, bizType)
                .remove();
         */
        //=====================================================================
        //代码改造,redis优化 (+ mq消息中间件解耦合 --- 后面有的)
        //=====================================================================

        //>>>> 从redis缓存移除 <<<<<<<===========================================
        String key = RedisConstants.LIKES_BIZ_KEY_PREFIX + bizType + ":" + bizId;
        Long remove = redisTemplate.opsForSet().remove(key, userId.toString());
        if (remove != null) {
            return remove > 0;
        }
        return false;
    }
    //点赞

    private Boolean like(LikeRecordFormDTO dto, Long userId) {
        String bizType = dto.getBizType();
        Long bizId = dto.getBizId();
        //判断数据库是否有
        //没有就新增,有就返回false
        //>>>>>原始方法<<<<<<===================================================
        /*
            LikedRecord one = lambdaQuery().eq(LikedRecord::getUserId, userId)
                    .eq(LikedRecord::getBizId, bizId)
                    .eq(LikedRecord::getBizType, bizType)
                    .one();
            if (one != null) {
                return false;
            }
            LikedRecord likedRecord = BeanUtils.copyBean(dto, LikedRecord.class);
            likedRecord.setUserId(userId);
            save(likedRecord);
            return true;
        */

        //=====================================================================
        //代码改造,redis优化 (+ mq消息中间件解耦合 --- 后面有的)
        //=====================================================================

        //>>>> 加入redis缓存 , 使用set存储,可以去重复点赞 <<<<<<<====================
        String key = RedisConstants.LIKES_BIZ_KEY_PREFIX + bizType + ":" + bizId;
        Long add = redisTemplate.opsForSet().add(key, userId.toString());
        if (add != null) {
            return add > 0;
        }
        return false;
    }

    /**
     * 查询点赞状态
     *
     * @param bizType
     * @param bizIds
     * @return
     */
    @Override
    public Set<Long> selectLikesStatus(String bizType, List<Long> bizIds) {
        //原始代码==============================================================
        /*
            Long userId = UserContext.getUser();
            //查询复合条件的
            List<LikedRecord> list = lambdaQuery().eq(LikedRecord::getUserId, userId)
                    .eq(LikedRecord::getBizType, bizType)
                    .in(LikedRecord::getBizId, bizIds)
                    .list();
            if (CollUtils.isEmpty(list)){
                return CollUtils.emptySet();
            }
        */
        //=====================================================================
        //代码改造,redis优化
        //=====================================================================

        //改造后的代码===========================================================
        Long userId = UserContext.getUser();
        Set<Long> ids = new HashSet<>();
        List<Object> objects = redisTemplate.executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection conn) throws DataAccessException {
                StringRedisTemplate connection = (StringRedisTemplate) conn;

                for (Long bizId : bizIds) {
                    String key = RedisConstants.LIKES_BIZ_KEY_PREFIX + bizType + ":" + bizId;
                    connection.opsForSet().isMember(key, userId);
                }
                return null;
            }
        });

        if (CollUtils.isEmpty(objects)) {
            return CollUtils.emptySet();
        }
        for (int i = 0; i < bizIds.size(); i++) {
            Boolean o = (Boolean) objects.get(i);
            //如果能查出来, 即为 true ,说明用户点赞过, 将该id 添加到set集合返回;
            if (o) {
                ids.add(bizIds.get(i));
            }
        }

        return ids;
    }

    /**
     * 将redis里面ZSet的点赞数更新到 回答评论表
     *
     * @param s
     * @param maxBizSize
     */
    @Override
    public void readLikedTimesAndSendMessage(String s, Integer maxBizSize) {
        //=====================================================================
        //代码改造,redis优化 (+ mq消息中间件解耦合 --- 后面有的)
        //=====================================================================
        String key = RedisConstants.LIKES_TIMES_KEY_PREFIX + s;
        Set<ZSetOperations.TypedTuple<String>> typedTuples = redisTemplate.opsForZSet().popMin(key, maxBizSize);
        if (CollUtils.isEmpty(typedTuples)) {
            return;
        }
        List<LikedTimesDTO> likedTimesDTOList = new ArrayList<>();
        for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
            String value = typedTuple.getValue();
            Double score = typedTuple.getScore();
            //为空就跳过此次循环
            if (value == null || score == null) {
                continue;
            }
            LikedTimesDTO dto = new LikedTimesDTO();
            Long bizId = Long.valueOf(value);
            Integer likeTimes = score.intValue();
            //封装 bizId likeTimes
            dto.setBizId(bizId);
            dto.setLikedTimes(likeTimes);
            likedTimesDTOList.add(dto);
        }
        //使用 MQ 发送消息到 对应模块 , 修改点赞数
        mqHelper.send(
                MqConstants.Exchange.LIKE_RECORD_EXCHANGE,
                StringUtils.format(MqConstants.Key.LIKED_TIMES_KEY_TEMPLATE, s),
                likedTimesDTOList
        );
    }
}
